<?php
/**
 * Zend Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Zend
 * @package    Zend_Filter
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 * @version    $Id: Zip.php 23775 2011-03-01 17:25:24Z ralph $
 */

/**
 * @see Zend_Filter_Compress_CompressAbstract
 */
require_once 'Zend/Filter/Compress/CompressAbstract.php';

/**
 * Compression adapter for zip
 *
 * @category   Zend
 * @package    Zend_Filter
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
class Zend_Filter_Compress_Zip extends Zend_Filter_Compress_CompressAbstract {
	/**
	 * Compression Options
	 * array(
	 * 'archive'  => Archive to use
	 * 'password' => Password to use
	 * 'target'   => Target to write the files to
	 * )
	 *
	 * @var array
	 */
	protected $_options = array ('archive' => null, 'target' => null );
	
	/**
	 * Class constructor
	 *
	 * @param string|array $options (Optional) Options to set
	 */
	public function __construct($options = null) {
		if (! extension_loaded ( 'zip' )) {
			require_once 'Zend/Filter/Exception.php';
			throw new Zend_Filter_Exception ( 'This filter needs the zip extension' );
		}
		parent::__construct ( $options );
	}
	
	/**
	 * Returns the set archive
	 *
	 * @return string
	 */
	public function getArchive() {
		return $this->_options ['archive'];
	}
	
	/**
	 * Sets the archive to use for de-/compression
	 *
	 * @param string $archive Archive to use
	 * @return Zend_Filter_Compress_Rar
	 */
	public function setArchive($archive) {
		$archive = str_replace ( array ('/', '\\' ), DIRECTORY_SEPARATOR, $archive );
		$this->_options ['archive'] = ( string ) $archive;
		
		return $this;
	}
	
	/**
	 * Returns the set targetpath
	 *
	 * @return string
	 */
	public function getTarget() {
		return $this->_options ['target'];
	}
	
	/**
	 * Sets the target to use
	 *
	 * @param string $target
	 * @return Zend_Filter_Compress_Rar
	 */
	public function setTarget($target) {
		if (! file_exists ( dirname ( $target ) )) {
			require_once 'Zend/Filter/Exception.php';
			throw new Zend_Filter_Exception ( "The directory '$target' does not exist" );
		}
		
		$target = str_replace ( array ('/', '\\' ), DIRECTORY_SEPARATOR, $target );
		$this->_options ['target'] = ( string ) $target;
		return $this;
	}
	
	/**
	 * Compresses the given content
	 *
	 * @param  string $content
	 * @return string Compressed archive
	 */
	public function compress($content) {
		$zip = new ZipArchive ();
		$res = $zip->open ( $this->getArchive (), ZipArchive::CREATE | ZipArchive::OVERWRITE );
		
		if ($res !== true) {
			require_once 'Zend/Filter/Exception.php';
			throw new Zend_Filter_Exception ( $this->_errorString ( $res ) );
		}
		
		if (file_exists ( $content )) {
			$content = str_replace ( array ('/', '\\' ), DIRECTORY_SEPARATOR, realpath ( $content ) );
			$basename = substr ( $content, strrpos ( $content, DIRECTORY_SEPARATOR ) + 1 );
			if (is_dir ( $content )) {
				$index = strrpos ( $content, DIRECTORY_SEPARATOR ) + 1;
				$content .= DIRECTORY_SEPARATOR;
				$stack = array ($content );
				while ( ! empty ( $stack ) ) {
					$current = array_pop ( $stack );
					$files = array ();
					
					$dir = dir ( $current );
					while ( false !== ($node = $dir->read ()) ) {
						if (($node == '.') || ($node == '..')) {
							continue;
						}
						
						if (is_dir ( $current . $node )) {
							array_push ( $stack, $current . $node . DIRECTORY_SEPARATOR );
						}
						
						if (is_file ( $current . $node )) {
							$files [] = $node;
						}
					}
					
					$local = substr ( $current, $index );
					$zip->addEmptyDir ( substr ( $local, 0, - 1 ) );
					
					foreach ( $files as $file ) {
						$zip->addFile ( $current . $file, $local . $file );
						if ($res !== true) {
							require_once 'Zend/Filter/Exception.php';
							throw new Zend_Filter_Exception ( $this->_errorString ( $res ) );
						}
					}
				}
			} else {
				$res = $zip->addFile ( $content, $basename );
				if ($res !== true) {
					require_once 'Zend/Filter/Exception.php';
					throw new Zend_Filter_Exception ( $this->_errorString ( $res ) );
				}
			}
		} else {
			$file = $this->getTarget ();
			if (! is_dir ( $file )) {
				$file = basename ( $file );
			} else {
				$file = "zip.tmp";
			}
			
			$res = $zip->addFromString ( $file, $content );
			if ($res !== true) {
				require_once 'Zend/Filter/Exception.php';
				throw new Zend_Filter_Exception ( $this->_errorString ( $res ) );
			}
		}
		
		$zip->close ();
		return $this->_options ['archive'];
	}
	
	/**
	 * Decompresses the given content
	 *
	 * @param  string $content
	 * @return string
	 */
	public function decompress($content) {
		$archive = $this->getArchive ();
		if (file_exists ( $content )) {
			$archive = str_replace ( array ('/', '\\' ), DIRECTORY_SEPARATOR, realpath ( $content ) );
		} elseif (empty ( $archive ) || ! file_exists ( $archive )) {
			require_once 'Zend/Filter/Exception.php';
			throw new Zend_Filter_Exception ( 'ZIP Archive not found' );
		}
		
		$zip = new ZipArchive ();
		$res = $zip->open ( $archive );
		
		$target = $this->getTarget ();
		
		if (! empty ( $target ) && ! is_dir ( $target )) {
			$target = dirname ( $target );
		}
		
		if (! empty ( $target )) {
			$target = rtrim ( $target, '/\\' ) . DIRECTORY_SEPARATOR;
		}
		
		if (empty ( $target ) || ! is_dir ( $target )) {
			require_once 'Zend/Filter/Exception.php';
			throw new Zend_Filter_Exception ( 'No target for ZIP decompression set' );
		}
		
		if ($res !== true) {
			require_once 'Zend/Filter/Exception.php';
			throw new Zend_Filter_Exception ( $this->_errorString ( $res ) );
		}
		
		if (version_compare ( PHP_VERSION, '5.2.8', '<' )) {
			for($i = 0; $i < $zip->numFiles; $i ++) {
				$statIndex = $zip->statIndex ( $i );
				$currName = $statIndex ['name'];
				if (($currName {0} == '/') || (substr ( $currName, 0, 2 ) == '..') || (substr ( $currName, 0, 4 ) == './..')) {
					require_once 'Zend/Filter/Exception.php';
					throw new Zend_Filter_Exception ( 'Upward directory traversal was detected inside ' . $archive . ' please use PHP 5.2.8 or greater to take advantage of path resolution features of ' . 'the zip extension in this decompress() method.' );
				}
			}
		}
		
		$res = @$zip->extractTo ( $target );
		if ($res !== true) {
			require_once 'Zend/Filter/Exception.php';
			throw new Zend_Filter_Exception ( $this->_errorString ( $res ) );
		}
		
		$zip->close ();
		return $target;
	}
	
	/**
	 * Returns the proper string based on the given error constant
	 *
	 * @param string $error
	 */
	protected function _errorString($error) {
		switch ($error) {
			case ZipArchive::ER_MULTIDISK :
				return 'Multidisk ZIP Archives not supported';
			
			case ZipArchive::ER_RENAME :
				return 'Failed to rename the temporary file for ZIP';
			
			case ZipArchive::ER_CLOSE :
				return 'Failed to close the ZIP Archive';
			
			case ZipArchive::ER_SEEK :
				return 'Failure while seeking the ZIP Archive';
			
			case ZipArchive::ER_READ :
				return 'Failure while reading the ZIP Archive';
			
			case ZipArchive::ER_WRITE :
				return 'Failure while writing the ZIP Archive';
			
			case ZipArchive::ER_CRC :
				return 'CRC failure within the ZIP Archive';
			
			case ZipArchive::ER_ZIPCLOSED :
				return 'ZIP Archive already closed';
			
			case ZipArchive::ER_NOENT :
				return 'No such file within the ZIP Archive';
			
			case ZipArchive::ER_EXISTS :
				return 'ZIP Archive already exists';
			
			case ZipArchive::ER_OPEN :
				return 'Can not open ZIP Archive';
			
			case ZipArchive::ER_TMPOPEN :
				return 'Failure creating temporary ZIP Archive';
			
			case ZipArchive::ER_ZLIB :
				return 'ZLib Problem';
			
			case ZipArchive::ER_MEMORY :
				return 'Memory allocation problem while working on a ZIP Archive';
			
			case ZipArchive::ER_CHANGED :
				return 'ZIP Entry has been changed';
			
			case ZipArchive::ER_COMPNOTSUPP :
				return 'Compression method not supported within ZLib';
			
			case ZipArchive::ER_EOF :
				return 'Premature EOF within ZIP Archive';
			
			case ZipArchive::ER_INVAL :
				return 'Invalid argument for ZLIB';
			
			case ZipArchive::ER_NOZIP :
				return 'Given file is no zip archive';
			
			case ZipArchive::ER_INTERNAL :
				return 'Internal error while working on a ZIP Archive';
			
			case ZipArchive::ER_INCONS :
				return 'Inconsistent ZIP archive';
			
			case ZipArchive::ER_REMOVE :
				return 'Can not remove ZIP Archive';
			
			case ZipArchive::ER_DELETED :
				return 'ZIP Entry has been deleted';
			
			default :
				return 'Unknown error within ZIP Archive';
		}
	}
	
	/**
	 * Returns the adapter name
	 *
	 * @return string
	 */
	public function toString() {
		return 'Zip';
	}
}
